package com.cjq.filter;

import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.cjq.utils.JwtUtils;
import com.cjq.utils.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * 每次请求都会走这个方法，jwt从header中带过来
 * 在此处解析jwt，认证成功则吧authentication信息设置到SecurityContextHolder中
 * 因为需要解析，故相比与session用计算力来换取空间
 * 因为session状态，故每次请求时都需要解析token得到认证信息，并把它放在SecurityContextHolder中，效率较低
 * @author junqiancai@foxmail.com
 * @since 2021/6/10 14:40
 */

@Component
public class JwtTokenCheckFilter extends OncePerRequestFilter {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {
        res.setContentType("application/json;charset=utf-8");
        String uri = req.getRequestURI();
        String method = req.getMethod();
        //登录请求，直接放行
        if ("/user/login".equals(uri) && "POST".equalsIgnoreCase(method)) {
            filterChain.doFilter(req,res);
            return;
        }
        //第三方认证登录请求回调，直接放行
        if ("/login/dgut".equals(uri) && "GET".equalsIgnoreCase(method)) {
            filterChain.doFilter(req,res);
            return;
        }
        //非登录请求，验证jwt
        //去掉前缀活动token
        String token = req.getHeader(JwtUtils.AUTHENTICATION);
        if(StringUtils.hasText(token)){
            token = token.replace("bearer ", "");
            //从redis中获取key，判断有无，无则表示已经登出
            Boolean hasKey = redisTemplate.hasKey(JwtUtils.REDIS_PREFIXED + token);
            if(hasKey==null || !hasKey){
                //说明已经登出
                //在controller中会抛异常需要捕获，此处无？？？
                String result = new ObjectMapper().writeValueAsString(Result.fail(Result.NOT_AUTH, "请先登录"));
                PrintWriter writer = res.getWriter();
                writer.write(result);
                writer.flush();
                writer.close();
                return;
            }
            //解析token
            DecodedJWT decodedJWT=null;
            try {
                decodedJWT = JwtUtils.decodeJwt(token);
            }catch (JWTVerificationException e){
                //token验证失败
                e.printStackTrace();
                ObjectMapper objectMapper = new ObjectMapper();
                //在controller中会抛异常需要捕获，此处无？？？
                String result = new ObjectMapper().writeValueAsString(Result.fail(Result.NOT_AUTH, "请先登录"));
                PrintWriter writer = res.getWriter();
                writer.write(result);
                writer.flush();
                writer.close();
                return;
            }
//        assert decodedJWT!=null; //编译器提示恒为true
            //获取用户名和id以及权限
            String username = decodedJWT.getClaim("username").asString();
            Integer userId = decodedJWT.getClaim("id").asInt();
            Integer roleId = decodedJWT.getClaim("role").asInt();
            HashMap<String, Object> details = new HashMap<>();
            details.put("userId",userId);
            //把roleId转为SimpleGrantedAuthority
            ArrayList<SimpleGrantedAuthority> authorities = new ArrayList<>();
            String loginRole = "ROLE_";
            if(roleId==0)loginRole += "SUPER";
            else if(roleId==1)loginRole += "ADMIN";
            else loginRole += "USER";
            authorities.add(new SimpleGrantedAuthority(loginRole));
            //构造Authentication放入上下标识认证通过
            UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, null, authorities);
            //保存额外的信息
            authToken.setDetails(details);
            SecurityContextHolder.getContext().setAuthentication(authToken);
            //放行
            filterChain.doFilter(req,res);
            return;
        }
        //没有token，返回错误信息
        String result = new ObjectMapper().writeValueAsString(Result.fail(Result.NOT_AUTH, "请先登录"));
        PrintWriter writer = res.getWriter();
        writer.write(result);   //认证失败，没有token
        writer.flush();
        writer.close();
    }
}
